-module(agent_httpd_proxy).


-behaviour(gen_server).

%% API
-export([start_link/0]).
-export([get_stat_data/2]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
         terminate/2, code_change/3]).
-include("agent.hrl").
-include_lib("stdlib/include/qlc.hrl").
-record(state, {}).


get_stat_data(Container,TimeOut) when is_record(Container,container)->
        gen_server:call(?MODULE,{get_stat_data ,Container },TimeOut).


%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

%%====================================================================
%% gen_server callbacks
%%====================================================================

%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%%                         {ok, State, Timeout} |
%%                         ignore               |
%%                         {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([]) ->
    {ok, #state{}}.

     
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%%                                      {reply, Reply, State, Timeout} |
%%                                      {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, Reply, State} |
%%                                      {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call({get_stat_data , term } =Msg, _From,State )->
  %%     error_logger:info_msg("agent_httpd_proxy recving a msg :~p~n",[Msg]),
      Result =    mnesia_op:do(qlc:q([{X#app_term_runtime.app,
                               X#app_term_runtime.term_runtime} ||
                              X <- mnesia:table(app_term_runtime)])),
      mnesia_op:clear_table(app_term_runtime),
      {reply, Result,State};
handle_call({get_stat_data , total } =Msg, _From,State )->   
       error_logger:info_msg("agent_httpd_proxy recving a msg :~p~n",[Msg]),
       {reply , 
                 mnesia_op:do(qlc:q([{X#app_total_runtime.app,
                               X#app_total_runtime.total_runtime} ||
                              X <- mnesia:table(app_total_runtime)])),
                       State };
          
handle_call({get_stat_data, Container} , _From , State ) when is_record(Container,container)->

        case mnesia_op:do(qlc:q([ X#httpd_proc.port ||
                              X <- mnesia:table(httpd_proc)])) of
              [] ->  { reply , [] ,State }  ;
              Ports ->
                         %%       error_logger:info_msg("Httpd ports:~p~n",[Ports]),
                                Instance2Runtime = ets:new(?MODULE,[ordered_set,private]),                        
                                lists:foreach( fun (Port)->
                                        loop ({Instance2Runtime,Container,Port} )
                                        end , Ports),
                                %%本采样周期的数据记录到累计
                                L=ets:tab2list(Instance2Runtime),
                                add_term_instance_runtime(L),
                                add_total_instance_runtime(L),
                                                                                        
                                TotalCpu =   lists:foldl( fun({ _Instance , #runtime{cpu=Cpu}=_Runtime  }, Sum ) ->
                                        Sum + Cpu
                                                end , 0 ,  ets:tab2list(Instance2Runtime) ),
%%                                error_logger:info_msg("totalCpu=~p~n",[TotalCpu]),
%%                                error_logger:info_msg("Instance2Runtime=~p~n",[ ets:tab2list(Instance2Runtime) ]),
                                ets:delete(Instance2Runtime),
                                {reply,
                                        lists:map(  fun( { Instance,Runtime} ) ->
                                           Cpu=case TotalCpu of
                                                0 ->
                                                       0;
                                                _ ->
                                                    Runtime#runtime.cpu /TotalCpu                        
                                                 end,
					error_logger:info_msg("after calculate,cpu : ~p~n",[Cpu]),
                                                  #instance_runtime {
                                                     instance=Instance,
                                                     runtime=Runtime#runtime{cpu=Cpu}
                                                  }
                                        end , L) ,
                                        State }                                               
        end;
handle_call(_Request, _From, State) ->
    Reply = ok,
    {reply, Reply, State}.

%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
    {noreply, State}.

%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%%                                       {noreply, State, Timeout} |
%%                                       {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info(_Info, State) ->
    {noreply, State}.

%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
    ok.

%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
loop({Instance2Runtime , Container,Port}) ->
      error_logger:info_msg("enter agent_httpd_proxy:loop, port=~p~n",[Port]),
        case gen_tcp:connect("localhost",Port,[binary,{packet,0}] ) of
                {ok,Socket}->
                        case gen_tcp:send(Socket,"command=get_stat_data") of
                                ok ->
                                         parse_data({Instance2Runtime,Container},
                                                                binary_to_list(  receive_data(Socket,[])));
                                { error ,Reason } ->
                                        {error ,Reason }                                        
                        end;
                {error , timeout } ->
                        error_logger:info_msg("connect the httpd process listening at port:~p fail because of timeout ~n",
                                        [Port]),
                        {error , timeout } ;
                {error, econnrefused } ->
                        error_logger:info_msg("the httpd process listening at port:~p has been killed , Reaons=~p~n",
                                        [Port,econnrefused]),
                        mnesia_op:delete(httpd_proc,Port)      
        end.
receive_data(Socket,SoFar)->
        receive
                {tcp,Socket,Bin} ->
	%%		error_logger:info_msg("agent_httpd_proxy:recving Bin=~p~n",[Bin]),
                        receive_data(Socket, [Bin|SoFar]);
                {tcp_closed , Socket } ->
        %%		error_logger:info_msg("agent_httpd_proxy:tcp_closed~p~n",[SoFar]),
                        list_to_binary(lists:reverse(SoFar))
        after  200 ->
                        error_logger:info_msg("recving timeout~n"),
                        list_to_binary([])                        
        end.
%%app_name=xxx&cpu=xxx&up=xxx&down=xxx&pv=xx\r\n
%%app_name=xxx&cpu=xxx&up=xxx&down=xxx&pv=xx\r\n        
%%app_name=xxx&cpu=xxx&up=xxx&down=xxx&pv=xx\r\n                
parse_data({Instance2Runtime,Container}, Reponse) ->
	error_logger:info_msg("recving reponse:~p~n",[Reponse]),
        case  string:tokens(Reponse,"\r\n") of
               [] ->
                   []; 
               L  when is_list(L)->
                lists:foreach(fun(Line)  ->
%%                         error_logger:info_msg("process the Line=~p~n",[Line]),
                         case string:tokens(Line,"&") of
                                        [P1,P2,P3,P4,P5] ->
                                                ["domain",Domain]=string:tokens(P1,"="),
                                                ["cpu",Cpu]=string:tokens(P2,"="),
						{C,_} = string:to_integer(Cpu),
                                                ["up",Up]=string:tokens(P3,"="),
                                                {U,_} = string:to_integer(Up),
                                                ["down",Down]=string:tokens(P4,"="),
                                                {D,_} = string:to_integer(Down),
                                                ["pv",Pv]=string:tokens(P5,"="), 
                                                {P,_} = string:to_integer(Pv), 
                                                
                                                case  mnesia_op:do(qlc:q([X#app.name||
                                                                X<-mnesia:table(app),
                                                                X#app.domain=:=Domain])) of
                                                        [] ->
                                                                error_logger:info_msg("No such a app , domain=~p~n",[Domain]);
                                                        [AppName]->
                                                                Instance=#instance{
                                                                   app_name=AppName,
                                                                   container_name=Container#container.name},
                                                                %%注意，这时候的cpu是cpu时间，是一个绝对值

                                                             Runtime=#runtime{ cpu=C,up=U,down=D,pv=P},   
                %%                                              error_logger:info_msg("Runtime=~p~n",[Runtime]),
                                                                case ets:lookup(Instance2Runtime,Instance) of
                                                              [] ->
                                                                  ets:insert(Instance2Runtime ,
                                                                        {Instance,Runtime} );      
                                                              [{Instance,OldRuntime}]->
                                                                  error_logger:info_msg("Instance=~p,OldRuntime=~p~n",
                                                                        [Instance,OldRuntime]),      
                                                                  ets:insert(Instance2Runtime,
                                                                        {Instance ,
                                                                        #runtime{
                                                                           cpu=Runtime#runtime.cpu+OldRuntime#runtime.cpu,
                                                                           down=Runtime#runtime.down+OldRuntime#runtime.down,
                                                                           up=Runtime#runtime.up+OldRuntime#runtime.up,
                                                                           pv=Runtime#runtime.pv+OldRuntime#runtime.pv
                                                                       }})
                                                        end  
                                                end;
                                      Any ->
                                             error_logger:info_msg("parse the reponse error , reponse=~p,reason=~p~n", 
                                                                        [Line,Any])                   
                                                
                                           
                           end
                  end, L );
               Any ->

                   error_logger:info_msg("Any=~p~n",[Any]),
                   []
        end.  
        
        
add_term_instance_runtime(L)->
%%      	error_logger:info_msg("add_term_instance_runtime L:~p~n",[L]),
        lists:foreach(  fun( {#instance{app_name=AppName} ,     Runtime} ) ->
%%                        error_logger:info_msg("add_term_instance_runtime AppName:~p~n",[AppName]),
                        case  mnesia_op:do(qlc:q([  X#app_term_runtime.term_runtime 
                                                || X <- mnesia:table(app_term_runtime),
                                                      X#app_term_runtime.app=:=AppName
                                                ])) of
                                [] ->
  %%                                      error_logger:info_msg("insert a new term_runtime AppName:~p,term_runtime=~p~n",[AppName,Runtime]),
                                        mnesia_op:insert(#app_term_runtime{
                                                        app=AppName,
                                                        term_runtime=#term_runtime{
                                                                cpu_time=Runtime#runtime.cpu,
                                                                up=Runtime#runtime.up,
                                                                down=Runtime#runtime.down,
                                                                pv=Runtime#runtime.pv
                                                        }
                                                 });
                                [#term_runtime{cpu_time=CpuTime,up=Up,down=Down,pv=Pv}] ->
                                       
                                       mnesia_op:insert( #app_term_runtime{app=AppName,
                                                    term_runtime=#term_runtime{
                                                     cpu_time=CpuTime+Runtime#runtime.cpu,
                                                     up=Up+Runtime#runtime.up,
                                                     down=Down+Runtime#runtime.down,
                                                     pv=Pv+Runtime#runtime.pv
                                                        }
                                                })                                                
                        end
                end,L)  . 

add_total_instance_runtime(L) ->
  %%   error_logger:info_msg("add_total_instance_runtime L:~p~n",[L]),
        lists:foreach(  fun( {#instance{app_name=AppName} ,Runtime} ) ->
  %%                      error_logger:info_msg("add_total_instance_runtime AppName:~p~n",[AppName]),
  %%                      error_logger:info_msg("add_total_instance_runtime Runtime:~p~n",[Runtime]),
                        case  mnesia_op:do(qlc:q([  X#app_total_runtime.total_runtime ||
                                              X <- mnesia:table(app_total_runtime),
                                              X#app_total_runtime.app=:=AppName
                                                ])) of
                                [] ->
                                        mnesia_op:insert(#app_total_runtime{
                                                        app=AppName,
                                                        total_runtime=#total_runtime{
                                                                cpu_time=Runtime#runtime.cpu,
                                                                up=Runtime#runtime.up,
                                                                down=Runtime#runtime.down,
                                                                pv=Runtime#runtime.pv
                                                        }
                                                 });
                                [#total_runtime{cpu_time=CpuTime,up=Up,down=Down,pv=Pv}] ->
                                       mnesia_op:insert( #app_total_runtime{app=AppName,
                                                    total_runtime=#total_runtime{
                                                     cpu_time=CpuTime+Runtime#runtime.cpu,
                                                     up=Up+Runtime#runtime.up,
                                                     down=Down+Runtime#runtime.down,
                                                     pv=Pv+Runtime#runtime.pv
                                                        }
                                                })                                                
                        end
                end,L)  .             

